/***********************************************************************
 * BinTrotter : BitTorrent library/client for the Android operating system.
 *  
 * Copyright (C) 2009, Christopher David Stones (chris.stones@gmail.com)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************/

package org.bintrotter.tracker;

import org.bintrotter.Main;
import org.bintrotter.bdecode.*;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.LinkedList;
import java.util.List;

public class Response {

	public class Peer
	{
		public byte[] peer_id = null;
		public String ip = null;;
		public int port = 0;
		
		public Peer(Node<BCodeTree.NodeData> dict) {
		
			Node<BCodeTree.NodeData> tempNode;
			
			if((tempNode = TreeUtils.lookupInDictionary(dict, "peer id"))!=null)
				peer_id = tempNode.data.getString();
			
			if((tempNode = TreeUtils.lookupInDictionary(dict, "ip"))!=null)
				ip = new String( tempNode.data.getString() );
			
			if((tempNode = TreeUtils.lookupInDictionary(dict, "port"))!=null)
				port = (int)tempNode.data.getLong();
		}
	
		public Peer(DataInputStream is) throws IOException {
				
			peer_id = null;
			byte[] ip = new byte[4];
			is.read(ip);
			
			int _ip1 = (int)ip[0];
			int _ip2 = (int)ip[1];
			int _ip3 = (int)ip[2];
			int _ip4 = (int)ip[3];
			
			if(_ip1<0)
				_ip1+=128;
			if(_ip2<0)
				_ip2+=128;
			if(_ip3<0)
				_ip3+=128;
			if(_ip4<0)
				_ip4+=128;
			
			this.ip = String.format("%d.%d.%d.%d", _ip1,_ip2,_ip3,_ip4);
			port = is.readUnsignedShort();
		}
		
		public SocketAddress socktAddress() {
			
			return new InetSocketAddress(ip,port);
		}
	}
	
	public String 		failure_reason;
	public String 		warning_message;
	public long	  		interval;
	public long	  		min_interval;
	public byte[] 		tracker_id;
	public long	  		complete;
	public long	  		incomplete;
	public List<Peer> 	peers;
		
	public Response( InputStream is ) throws Throwable {
		
		BCodeTree tree = new BCodeTree(is);
			
		Node<BCodeTree.NodeData> root = tree.rootNode.child;
		
		failure_reason = null;
		warning_message = null;
		interval = 0;
		min_interval = 0;
		tracker_id = null;
		complete = 0;
		incomplete = 0;
		peers = null;
		
		if(checkFailure( root ) == false) {
			
			checkWarning( root );
			checkMinInterval( root );
			checkTrackerId( root );
			
			interval   = TreeUtils.lookupInDictionary( root, "interval").data.getLong();
			
			/*** not all trackers are filling in the complete / incomplete fields 
			 * the specification does not list this field as optional.
			 */
			Node<BCodeTree.NodeData> tempNode;
			if((tempNode = TreeUtils.lookupInDictionary( root, "complete")) != null)
				complete   = tempNode.data.getLong();
			
			if((tempNode = TreeUtils.lookupInDictionary( root, "incomplete")) != null)
				incomplete = tempNode.data.getLong();
			
			Node<BCodeTree.NodeData> peers = TreeUtils.lookupInDictionary(root, "peers");
			
			switch(peers.data.getType())
			{
			case String:
				parsePeers( peers.data.getString() );
				return;
			case List:
				parsePeers( peers );
				return;
			default:
				throw new Exception("unknown peers format");
			}
		}
	}

	
	
	private void parsePeers(byte[] binaryFormat) throws IOException {
		
		DataInputStream dis = new DataInputStream(new ByteArrayInputStream(binaryFormat));
		
		peers = new LinkedList<Peer>();
		
		while( dis.available() > 0) {
			
			peers.add( new Peer( dis ) );
		}
	}
	
	private void parsePeers(Node<BCodeTree.NodeData> peerList) {
		
		peers = new LinkedList<Peer>();
		
		peerList = peerList.child;
		
		while(peerList != null) {
			
			peers.add( new Peer(peerList) );
			peerList = peerList.sibling;
		}
	}
	
	private boolean checkFailure(Node<BCodeTree.NodeData> root) {
		
		Node<BCodeTree.NodeData> node = TreeUtils.lookupInDictionary(root, "failure reason");
		if(node != null) {
			
			failure_reason = new String(node.data.getString());
			
			Main.log.critical("Faulure: " + failure_reason);
			return true;
		}
		return false;
	}
	
	private void checkWarning(Node<BCodeTree.NodeData> root) {
		
		Node<BCodeTree.NodeData> node = TreeUtils.lookupInDictionary(root, "warning message");
		if(node != null) {
			
			warning_message = new String(node.data.getString());
			Main.log.warn("Warning: " + warning_message);
		}
	}

	
	private void checkMinInterval(Node<BCodeTree.NodeData> root) {
		
		Node<BCodeTree.NodeData> node = TreeUtils.lookupInDictionary(root, "min interval");
		if(node != null) {
			
			min_interval = node.data.getLong();
		}
	}
	
	private void checkTrackerId(Node<BCodeTree.NodeData> root) {
		
		Node<BCodeTree.NodeData> node = TreeUtils.lookupInDictionary(root, "tracker id");
		if(node != null) {
			
			tracker_id = node.data.getString();
		}
	}
}

